這句話幾乎是每個團隊都講過的話。測試涵蓋了主要情境、CI 顯示全綠、PR 描述寫得清清楚楚——這時候要求「再花時間仔細 review 一次設計」,聽起來像是在浪費時間。
但我想先問一個問題:測試綠燈證明的是「這段程式碼做了什麼」,還是「這段程式碼該不該長這樣」? 這兩件事聽起來很像,其實是完全不同維度的問題,而 AI 生成的程式碼最擅長讓第一件事成立,同時完全不管第二件事。
ai-news-test 兩個版本同樣通過測試、卻天差地遠的實際案例說明這個落差一個驗收測試的結構通常是 Given-When-Then:給定某個狀態,做某個操作,得到某個結果。它驗證的是輸入跟輸出之間的對應關係——這篇文章可以發佈、那篇文章不能重複發佈。測試完全不管「這個結果是用一行邏輯算出來的,還是繞過八層抽象才算出來的」。
這不是測試設計得不夠好,這是測試這個工具本來就只負責檢查行為,不負責檔案數、呼叫鏈長度、類別職責是否清楚。用測試去檢查設計品質,就像用體溫計去量血壓——工具本身沒有問題,是拿錯工具去回答不屬於它的問題。
ai-news-test 這個示範專案的兩個版本都通過同一組 10 個驗收測試,測試檔案一行都沒改。乾淨版本 3 個檔案、249 行;過度設計版本 745 個檔案、21,727 行。如果你只看 CI 畫面:
Tests: 10 passed
兩個版本看起來一模一樣。但如果你去改一個小需求——替文章加上「分類」欄位——代價完全不同。乾淨版本只需要動 Article.php(建構子跟 getter)、ArticleRepository.php(SQL 與資料對應)、PublishArticleService.php(方法簽名),3 個檔案。過度設計版本光是在「真正有被呼叫」的 62 個檔案範圍內,就要動到 Aggregate、TableGateway、SQL Builder、Mapper、兩組 Command/Handler/Validator、Service 本體,大約 10-11 個檔案。
需求複雜度沒有變,只是多一個欄位,改動幅度卻差了 3-4 倍。這組「改動範圍」的落差,測試完全看不出來,因為兩個版本現在都通過同樣的測試——測試只在乎「文章能不能發佈」,不在乎「加一個欄位要動幾個檔案」。
過度設計版本裡有一個很典型的例子:SqliteUnitOfWork 這個類別。
❌ 名字承諾了交易保護,實際上什麼都沒做:
class SqliteUnitOfWork implements UnitOfWorkInterface
{
public function begin(): void { $this->logger->log('begin (no-op)'); }
public function commit(): void { $this->logger->log('commit (no-op)'); }
public function rollback(): void { $this->logger->log('rollback (no-op)'); }
}
所有測試依然全部通過——因為驗收測試的情境裡沒有一條特別去驗證「多筆寫入中途失敗時會不會回滾」。測試綠燈,不代表這裡沒有問題;測試只是沒有問到這一題。如果系統真的需要跨多筆寫入的一致性保證,這裡完全沒有提供,但沒有任何一個測試會亮紅燈告訴你。
✅ 更保守的作法:如果 review 時發現一個類別名稱承諾了某個保證(交易、重試、快取一致性),至少要反問一句「有沒有一條測試專門驗證這個保證真的存在」,而不是預設「有這個類別就代表有這個能力」。
我們可以把「該檢查什麼」拆成兩層:
測試通過只證明程式碼「能動」,不證明程式碼「該長這樣」。 這句話值得重複講,因為它正是本系列反覆要處理的落差:如果 review 的標準只停在「行為契約」,AI 生成的程式碼永遠可以合法地通過審查,同時累積出後面難以維護的設計問題。
人類工程師寫出「測試過但設計有問題」的程式碼,速度是有限的——一天能寫的行數就這麼多,設計問題累積的速度也慢。AI 生成程式碼的速度快了幾十倍甚至上百倍,如果 review 的判準還停在「測試綠燈就過」,設計層面的問題會用同樣的倍率累積,而人力 review 的速度沒有變快。這正是本系列的主題句:AI 沒有發明過度設計,它只是讓過度設計的速度追上了你按下 Enter 的速度;Review 要跟得上,審的就不能再是程式碼本身,而是產生程式碼的規則。 測試只是規則的其中一種,不是全部。
你的團隊 PR 檢查清單裡,有沒有一條是專門檢查「測試以外」的設計品質?如果拿掉 code owner 主觀的「這樣寫感覺怪怪的」判斷,你們有沒有客觀、可重複執行的方式去回答「這個改動範圍合不合理」?
ai-news-test 兩個版本同樣測試全過,但改一個欄位的代價差了 3-4 倍SqliteUnitOfWork 案例說明:類別名稱承諾的保證,測試沒有覆蓋到就不會被抓到Day 4 要往回看一段技術史:過度設計不是 AI 帶來的新病,這個問題在軟體工程界已經被討論了幾十年。今天先簡短回顧這段脈絡,順便釐清 AI 到底改變了什麼、沒改變什麼。
我自己也曾經被「CI 全綠」這四個字騙過好幾次——尤其是接手別人程式碼時,看到測試都過,會下意識放鬆戒心,直接開始改功能。後來吃過幾次虧才體會到:測試給的是「安全感」,不是「保證」,這兩者的差距,恰好就是設計品質留下的破口。 這個體會在 AI 開始大量參與寫程式碼之後變得更明顯,因為 AI 太擅長生產「能通過測試」的程式碼了,擅長到我必須提醒自己多問一句「這是真的沒問題,還是只是測試沒問到」。